package hu.arguscab.ai;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @param <E>
 * @param <N> Az objektum, amit a Node tárol
 * @author sharp
 */
public class GraphImpl<E, N> extends AbstractGraph<E, N> {

	private Node<N> root;

	private List<Node<N>> nodes = new ArrayList<Node<N>>();
	private Map<Node<N>, Set<Node<N>>> nodeMap = new HashMap<Node<N>, Set<Node<N>>>();

	private Map<Node<N>, Map<Node<N>, Edge<E>>> edgeMap = new HashMap<Node<N>, Map<Node<N>, Edge<E>>>();


	/**
	 * @see Graph#setRoot(hu.arguscab.AI.Node) 
	 */
	@Override
	public Node<N> setRoot(N root) {
		Node<N> node = new Node<N>(root) ;
		setRoot(node);
		return node;
	}
	
	@Override
	public void setRoot(Node<N> node) {
		this.root = node;
		node.setDepth(0);
		addNewNode(node);
	}

	/**
	 * Hozzáadja az uj node-ot a gráfhoz.
	 * Hozzáadja a listákhoz a node-nak megfelelő új listákat.
	 * @param node
	 */
	private void addNewNode(Node<N> node) {
		assert containsNode(node) == false;
		
		nodes.add(node);
		nodeMap.put(node, new HashSet<Node<N>>());
		edgeMap.put(node, new HashMap<Node<N>, Edge<E>>());

	}

	
	private void joinNodes(Node<N> from, Node<N> newNode, Edge<E> edge) {
		assert from != null && newNode != null && edge != null;
		nodeMap.get(newNode).add(from);
		nodeMap.get(from).add(newNode);
		
		edgeMap.get(newNode).put(from, edge);
		edgeMap.get(from).put(newNode, edge);
	}

	private void addEdge(Node<N> child, Node<N> parent, Edge<E> edge) {
		edgeMap.get(child).put(parent, edge);
		edgeMap.get(parent).put(child, edge);
	}

	/**
	 * @see Graph#addNode(hu.arguscab.AI.Node, hu.arguscab.AI.Node, hu.arguscab.AI.Edge)
	 * A 'from' node-nak már létezik child- és parentList-je
	 */
	@Override
	public void addNode(Node<N> from, Node<N> newNode, Edge<E> edge) {
		if ( from == null || newNode == null || edge == null )
			throw new IllegalArgumentException("All the nodes and edge must be not-null!");
		// A szülőt még ez elött hozzá kell adni, a addedNode miatt, hogy ott már legyen értéke
		if ( containsNode(from) == false )
			addNewNode(from);
		if ( containsNode(newNode) == false )
			addNewNode(newNode);

		joinNodes(from, newNode, edge);
	}

	/**
	 * @see Graph#addNodePair(hu.arguscab.AI.NodePair)
	 * @TODO: ha meghivja az addNode-ot, ott megint létrehoz Node-okat --> optimalizálni!
	 */
	@Override
	public void addNodePair( NodePair<E, N> pair ) {
		addNode( pair.getFrom(), pair.getTo(), pair.getEdge() ) ;
	}

	/**
	 * @see Graph#deleteNode(hu.arguscab.AI.Node) 
	 */
	@Override
	public void deleteNode(Node<N> node) {
		throw new UnsupportedOperationException("Not supported.");
	}

	/**
	 * @see Graph#getRoot() 
	 */
	@Override
	public Node<N> getRoot() {
		return root;
	}


	/**
	 * @see Graph#getEdge(hu.arguscab.AI.Node, hu.arguscab.AI.Node) 
	 */
	@Override
	public Edge<E> getEdge(Node<N> from, Node<N> to) {
		if ( from == null || to == null )
			throw new IllegalArgumentException("All the nodes and edge must be not-null!");
		Map<Node<N>, Edge<E>> map = edgeMap.get(from);
		if ( map == null )
			return null;
		Edge<E> edge = map.get(to);
		return edge;
	}

	/**
	 * @see Graph#degree(hu.arguscab.AI.Node) 
	 */
	@Override
	public int degree(Node<N> node) {
		return nodeMap.get(node).size();
	}

	/**
	 * @see Graph#adjacentTo(hu.arguscab.AI.Node) 
	 */
	@Override
	public Iterable<Node<N>> adjacentTo(Node<N> node) {
		return nodeMap.get(node);
	}

	/**
	 * @see Graph#containsNode(hu.arguscab.AI.Node) 
	 */
	@Override
	public boolean containsNode(Node<N> node) {
		return nodes.contains(node);
	}

	/**
	 * @see Graph#containsEdge(hu.arguscab.AI.Node, hu.arguscab.AI.Node) 
	 */
	@Override
	public boolean containsEdge(Node<N> from, Node<N> to) {
		if ( from == null || to == null )
			return false;
		return edgeMap.get(from).containsKey(to);
	}

	/**
	 * @see Graph#depthSearchIterator() 
	 */
	@Override
	public Iterable<Node<N>> depthSearchIterator() {
		// TODO: Implementing treespecific iterator
		return new DepthFirstSearchIterator<E, N>(this);
	}

	/**
	 * @see Graph#breathSearchIterator()
	 */
	@Override
	public Iterable<Node<N>> breathSearchIterator(Node<N> root) {
		// TODO: Implementing treespecific iterator
		return new BreadthSearchIterator<E, N>(this, root);
	}

	@Override
	public void clear() {
		nodeMap.clear();
		nodes.clear();
		edgeMap.clear();
		root = null;
	}
}
